#pragma once

#include <iostream>
#include <pthread.h>
#include <ctime>
#include <unistd.h>
#include <queue>

const int gcap = 5; // 阻塞队列的最大容量

template <class T>
class BlockQueue
{
public:
    BlockQueue(const int cap = gcap)
        : _cap(cap)
    {
        // 初始化互斥锁
        pthread_mutex_init(&_mutex, nullptr);
        // 初始化消费者、生产者的条件变量
        pthread_cond_init(&_consumerCond, nullptr);
        pthread_cond_init(&_productorCond, nullptr);
    }

    bool isFull()
    {
        return _q.size() == _cap;
    }

    bool isEmpty()
    {
        return _q.empty();
    }

    // 生产产品
    void push(const T &in)
    {
        // 加锁
        pthread_mutex_lock(&_mutex);

        // 判断阻塞队列是否为满
        while (isFull() == true) //使用while是防止，生产者误唤醒
        {
            // 队满：生产者停止生产产品，将生产者阻塞挂起（使用条件变量）
            // 1.锁资源只有一把，在此之前，生产者已经申请了锁，由此，在调用pthread_cond_wait时候，需要将锁传入
            // pthread_cond_wait函数内部会将生产者阻塞的时候，将锁资源归还（解锁）
            pthread_cond_wait(&_productorCond, &_mutex);
            // 阻塞其实就是将当前线程转移，调度其它线程。当消费者消费产品后，队列不满时，需要将生产者线程调度回来
            // 转移：就是将当前线程执行的代码位置和对应的数据转移，当线程调度回来时，这些资源也会一并恢复初始状态
            // 2.锁资源只有一把，pthread_cond_wait内部其实有两段含义：
            // 阻塞线程时会释放锁资源；OS在调度回线程时，pthread_cond_wait会为当前线程申请锁资源，只有申请成功才会唤醒线程
        }
        // 生产产品
        _q.push(in);
        // 到此行代码，当队列为空时又有产品了，消费者可能在休眠状态，唤醒消费者进行消费
        if (_q.size() >= _cap / 2) // 当产品大于阻塞队列的容量一半时，不管消费者是否阻塞，都唤醒消费者消费
            pthread_cond_signal(&_consumerCond);
        // 解锁
        pthread_mutex_unlock(&_mutex);
    }

    // 消费产品
    void pop(T *out)
    {
        // 加锁
        pthread_mutex_lock(&_mutex);

        // 情况与生产者相似
        while (isEmpty())
        {
            // 将消费者阻塞
            pthread_cond_wait(&_consumerCond, &_mutex);
        }
        // 消费产品
        *out = _q.front();
        _q.pop();

        // 到这里，队列的最后一个产品可能被消费完了，唤醒生产者，进行生产工作
        pthread_cond_signal(&_productorCond);
        // 解锁
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        // 释放锁
        pthread_mutex_destroy(&_mutex);
        // 释放条件变量
        pthread_cond_destroy(&_consumerCond);
        pthread_cond_destroy(&_productorCond);
    }

private:
    std::queue<T> _q;
    size_t _cap;            // 队列容量
    pthread_mutex_t _mutex; // 互斥锁

    // 1.队列为满时，生产者不需要再继续生产产品，应该处于休眠状态
    // 2.队列为空时，消费者不能获取产品进行消费，应该处于休眠状态

    // 为此，要为生产者、消费者专门设置条件变量
    pthread_cond_t _consumerCond;  // 消费者的条件变量
    pthread_cond_t _productorCond; // 生产者的条件变量
};
